﻿using System.Threading;
using System.Threading.Tasks;
using K9Nano.Caching;
using K9Nano.Serialize;

namespace Microsoft.Extensions.DependencyInjection
{
    public class DefaultCSRedisCache : ICache
    {
        protected readonly ISerializer Serializer;

        public DefaultCSRedisCache(ISerializer serializer)
        {
            this.Serializer = serializer;

            CSRedis.CSRedisClient.Serialize = o => serializer.SerializeToString(o);
            CSRedis.CSRedisClient.Deserialize = (s, type) => serializer.Deserialize(s, type);
        }

        public virtual bool Set(string key, object value, int expireSeconds = -1)
        {
            return RedisHelper.Set(key, value, expireSeconds);
        }

        public virtual Task<bool> SetAsync(string key, object value, int expireSeconds = -1, CancellationToken cancellation = default)
        {
            // https://github.com/2881099/csredis/issues/218
            var result = RedisHelper.Set(key, value, expireSeconds);
            return Task.FromResult(result);
        }

        public virtual T Get<T>(string key, int refreshSeconds = 0)
        {
            var t = RedisHelper.Get<T>(key);
            if (refreshSeconds > 0) RedisHelper.Expire(key, refreshSeconds);
            return t;
        }

        public virtual bool TryGet<T>(string key, out T result, int refreshSeconds = 0)
        {
            var value = RedisHelper.Get(key);
            if (value == null)
            {
                result = default(T);
                return false;
            }

            result = (T)RedisHelper.Instance.CurrentDeserialize(value, typeof(T));
            if (refreshSeconds > 0) Expire(key, refreshSeconds);
            return true;
        }

        public virtual async Task<T> GetAsync<T>(string key, int refreshSeconds = 0, CancellationToken cancellation = default)
        {
            var t = await RedisHelper.GetAsync<T>(key);
            if (refreshSeconds > 0) await RedisHelper.ExpireAsync(key, refreshSeconds);
            return t;
        }

        public virtual void Remove(params string[] key) => RedisHelper.Del(key);

        public virtual Task RemoveAsync(string key, CancellationToken cancellation = default) => RedisHelper.DelAsync(key);

        public virtual Task RemoveAsync(string[] keys, CancellationToken cancellation = default) => RedisHelper.DelAsync(keys);

        public virtual void RemovePattern(string pattern)
        {
            long cursor = 0;
            while (true)
            {
                var result = RedisHelper.Scan(cursor, pattern);
                if (result.Items.Length > 0)
                {
                    RedisHelper.Del(result.Items);
                }

                if (result.Cursor >= 0)
                {
                    cursor = result.Cursor;
                }
                else
                {
                    break;
                }
            }
        }

        public virtual async Task RemovePatternAsync(string pattern, CancellationToken cancellation = default)
        {
            long cursor = 0;
            while (true)
            {
                var result = await RedisHelper.ScanAsync(cursor, pattern);
                if (result.Items.Length > 0)
                {
                    await RedisHelper.DelAsync(result.Items);
                }

                if (result.Cursor >= 0)
                {
                    cursor = result.Cursor;
                }
                else
                {
                    break;
                }
            }
        }

        public virtual bool Expire(string key, int seconds) => RedisHelper.Expire(key, seconds);

        public virtual Task<bool> ExpireAsync(string key, int seconds, CancellationToken cancellation = default) => RedisHelper.ExpireAsync(key, seconds);
    }
}